package com.information.manage.basic.common.aspect;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.information.manage.basic.common.constant.Constant;
import com.information.manage.basic.common.result.Result;
import com.information.manage.basic.common.token.TokenUtils;
import com.information.manage.basic.common.utils.RedisUtils;
import com.information.manage.basic.mapper.*;
import com.information.manage.basic.model.dto.UserHotDataDTO;
import com.information.manage.basic.model.entity.*;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.annotation.Resource;
import javax.security.auth.message.AuthException;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * @author 92110
 */
@Aspect
@Component
@SuppressWarnings("all")
public class LogAspect {

    @Resource
    private LogInfoMapper logInfoMapper;

    private final Logger logger = LoggerFactory.getLogger(getClass());

    @Resource
    private SysUserMapper userMapper;
    @Resource
    private SysRoleMapper roleMapper;
    @Resource
    private SysRelUserRoleMapper relUserRoleMapper;

    @Resource
    private SysRelUserStaffMapper sysRelUserStaffMapper;

    @Resource
    private RelStaffDepMapper relStaffDepMapper;

    @Resource
    private RedisUtils redisUtils;

    /**
     * Controller aspect.
     */
    @Pointcut("execution(* com.information.manage.basic.controller..*.*(..))")
    public void controllerAspect() {
    }

    @Pointcut("execution(* com.information.manage.controller..*.*(..))")
    public void controllerFileAspect() {
    }

    @Pointcut("execution(* com.information.manage.basic.controller.SysUserController.login(*))")
    public void loginAspect() {
    }



    @AfterReturning(value = "loginAspect()", returning = "result")
    public void afterMethod(JoinPoint joinPoint, Result<String> result) throws Throwable {
        LogInfo logInfo = new LogInfo();
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        //防止不是http请求的方法，例如：scheduled
        if (ra == null) {
            return;
        }
        // 将返回值解析为对象,并获取token
        String token = result.getData();
        // jwt解析用户信息
        parseToken(logInfo, token);
        HttpServletRequest request = sra.getRequest();
        long startTime = System.currentTimeMillis();
        String startTimeFormatted = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTime);
        logInfo.setRequestTime(startTimeFormatted);
        logInfo.setRequestAddress(request.getRequestURI());
        logInfo.setIpAddress(request.getRemoteAddr());
        logInfo.setRequestMethod(joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        // 获取接口上的注解
        Method method = getCurrentMethod(joinPoint);
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        if (apiOperation != null) {
            // 打印注解的值
            logger.info("API描述: {}", apiOperation.value());
            logInfo.setLogDesc(apiOperation.value());
        }
        logger.debug("入参 : {}", logInfo);
        logInfoMapper.insert(logInfo);
    }

    private void parseToken(LogInfo logInfo, String token) {
        if (token != null) {
            //开始解析成UserDetails对象，如果得到的是null说明解析失败，JWT有问题
            Integer id = TokenUtils.getUserId(token);
            if(id != null) {
                UserHotDataDTO userCache = getUserCache(id);
                logInfo.setLogUser(userCache.getUserName());
                logInfo.setLogUserId(userCache.getUserId());
                if (userCache.getDepartmentId() != null) logInfo.setDepartmentId(userCache.getDepartmentId());
                if (StringUtils.isNotEmpty(userCache.getRoleName())) logInfo.setUserRole(userCache.getRoleName());
            }
        }
    }

    private UserHotDataDTO getUserCache(Integer userId){
        String redisKey = Constant.userHotDataKeyPrex + userId;
        if (!redisUtils.exists(redisKey)){
            UserHotDataDTO userHotDataDTO = new UserHotDataDTO();
            SysUserDO user = userMapper.selectById(userId);
            Assert.isTrue(user != null, "用户id:" + userId + "不存在");
            userHotDataDTO.setUserId(Long.valueOf(userId));
            userHotDataDTO.setUserName(user.getName());
            SysRelUserRoleDO sysRelUserRoleDO = relUserRoleMapper.selectOne(new LambdaQueryWrapper<SysRelUserRoleDO>().eq(SysRelUserRoleDO::getUserId, userId));
            if (sysRelUserRoleDO != null){
                Integer roleId = sysRelUserRoleDO.getRoleId();
                userHotDataDTO.setRoleId(Long.valueOf(roleId));
                SysRoleDO role = roleMapper.selectById(roleId);
                userHotDataDTO.setRoleName(role.getName());
            }
            SysRelUserStaffDO sysRelUserStaffDO = sysRelUserStaffMapper.selectOne(new LambdaQueryWrapper<SysRelUserStaffDO>().eq(SysRelUserStaffDO::getUserId, user.getId()));
            if (sysRelUserStaffDO != null){
                userHotDataDTO.setStaffId(Long.valueOf(sysRelUserStaffDO.getStaffId()));
                RelStaffDepDO relStaffDepDO = relStaffDepMapper.selectOne(new LambdaQueryWrapper<RelStaffDepDO>().eq(RelStaffDepDO::getStaffId, sysRelUserStaffDO.getStaffId()));
                if (relStaffDepDO != null) userHotDataDTO.setDepartmentId(Long.valueOf(relStaffDepDO.getDepartmentId()));
            }
            redisUtils.set(redisKey, userHotDataDTO, 2L, TimeUnit.HOURS);
            return userHotDataDTO;
        }else {
            UserHotDataDTO userHotDataDTO = (UserHotDataDTO) redisUtils.get(redisKey);
            if (userHotDataDTO == null){  // 这一时间点刚好过期
                return getUserCache(userId);
            }
            return userHotDataDTO;
        }
    }

    private Method getCurrentMethod(JoinPoint joinPoint) {
        String methodName = joinPoint.getSignature().getName();
        Class<?>[] parameterTypes = ((org.aspectj.lang.reflect.MethodSignature) joinPoint.getSignature()).getParameterTypes();
        try {
            return joinPoint.getTarget().getClass().getMethod(methodName, parameterTypes);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * Around 手动控制调用核心业务逻辑，以及调用前和调用后的处理,
     * <p>
     * 注意：当核心业务抛异常后，立即退出，转向AfterAdvice 执行完AfterAdvice，再转到ThrowingAdvice
     *
     * @param pjp the pjp
     * @return object
     * @throws Throwable the throwable
     */
    @Around(value = "controllerAspect() || controllerFileAspect()")
    public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {
        LogInfo logInfo = new LogInfo();
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        //防止不是http请求的方法，例如：scheduled
        if (ra == null) {
            return pjp.proceed();
        }
        long startTime = System.currentTimeMillis();
        String startTimeFormatted = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(startTime);
        HttpServletRequest request = sra.getRequest();
        String token = request.getHeader("token");
        // 如果是登录接口直接返回
        if (request.getRequestURI().contains("login")) {
            return pjp.proceed();
        }
        // jwt解析用户信息
        parseToken(logInfo, token);
        logInfo.setRequestAddress(request.getRequestURI());
        logInfo.setIpAddress(request.getRemoteAddr());
        logInfo.setRequestMethod(pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName());
        // 获取接口上的注解
        Method method = getCurrentMethod(pjp);
        ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);
        if (apiOperation != null) {
            // 打印注解的值
//            logger.info("API描述: {}", apiOperation.value());
            logInfo.setLogDesc(apiOperation.value());
        }

        try {
            Object response = pjp.proceed();
            logInfo.setRequestTime(startTimeFormatted);
            logger.debug("入参 : {}", logInfo);
            logInfoMapper.insert(logInfo);
            // 5.返回值打印
            return response;
        } catch (Throwable e) {
            // 1.异常打印
            logger.error("Exception : {}", e.getMessage(), e);
            // 2.异常类型判断
            if (e instanceof MethodArgumentNotValidException) {
                MethodArgumentNotValidException ex = (MethodArgumentNotValidException) e;
                logger.error("Exception : {}", Arrays.toString(ex.getBindingResult().getAllErrors().toArray()));
            } else if (e instanceof AuthException) {
                logger.error("Exception : {}", e.getMessage());
            } else {
                logger.error("Exception : {}", e.getMessage());
            }
            throw e;
        } finally {
            long endTime = System.currentTimeMillis();
            // 5.请求耗时打印
            logger.info("请求时间：{} , 请求耗时 : {}ms, API描述: {}", startTimeFormatted, (endTime - startTime), apiOperation != null ? apiOperation.value() : StringUtils.EMPTY);
        }
    }
}
